/* ==============================================================================
*
*   Copyright (c) 2010, Pierre Favier
*   All rights reserved.
*
*   NTasking is free software: you can redistribute it and/or modify it
*   under the terms of the GNU General Public License as published by the
*   Free Software Foundation.
*
*   Redistribution and use in source and binary forms, with or without
*   modification, are permitted provided that the following conditions
*   are met:
*
*   -   Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*   -   Redistributions in binary form must reproduce the above
*       copyright notice, this list of conditions and the following
*       disclaimer in the documentation and/or other materials
*       provided with the distribution.
*
*   THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
*   WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*   OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*   DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
*   BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
*   EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
*   TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
*   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
*   ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
*   TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
*   THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*   SUCH DAMAGE.
*
* ============================================================================== */

using System;
using System.Diagnostics;
using System.Threading;
using NTasking.Internals;
using Timeout = NTasking.Internals.Timeout;

namespace NTasking
{

    public abstract partial class Task : SynchronizedObject, ITask
    {

        #region internal types

        /// <summary>
        /// Define the various phases through which a task goes
        /// </summary>
        public enum TaskPhase
        {
            /// <summary>
            /// After instanciation
            /// </summary>
            Created = 0,
            /// <summary>
            /// During Actiaion
            /// </summary>
            Activating,
            /// <summary>
            /// Dung Bod execution
            /// </summary>
            Executing,
            /// <summary>
            /// After Body execution but before tread termination
            /// </summary>
            Completed,
            /// <summary>
            /// After tread termination
            /// </summary>
            Terminated
        }

        #endregion


        #region Static Fields

        /// <summary>
        /// Backup field for the CurrentTask property
        /// </summary>
        [ThreadStatic]
        private static ITask _currentTask;


        /// <summary>
        /// An object used as a lock to prevent concurrent creation of the _currentRealTask
        /// </summary>
        private static readonly object _globalLock = new object();

        #endregion


        #region Static Properties

        /// <summary>
        /// Gets or sets the current task.
        /// </summary>
        /// <value>The current task.</value>
        public static ITask CurrentTask
        {
            get
            {
                // If the current task does not exist, it means this property getter is called from
                // a Thread which has not been started by a Task.
                if (_currentTask == null)
                    lock (_globalLock)
                        if (_currentTask == null)
                            // Create a ThreadWrapper which encapsulates the current Thread
                            _currentTask = new ThreadWrapper();

                return _currentTask;
            }
            set
            {
                _currentTask = value;
            }
        }


        ///// <summary>
        ///// Gets or sets the current real task (not a ThreadWrapper).
        ///// </summary>
        ///// <value>The current real task or <c>null</c> if called from a ThreadWrapper.</value>
        //internal static Task CurrentRealTask
        //{
        //    get { return _currentRealTask; }
        //    set
        //    {
        //        _currentRealTask = value;
        //        CurrentTask = value;
        //    }
        //}


        #endregion


        #region Static Events

        /// <summary>
        /// A delegate used to handle the AnyTaskTerminated event
        /// </summary>
        public delegate void GlobalTaskTerminationHandler(Task task, TaskTerminationReason reason, Exception exception);


        /// <summary>
        /// Occurs when any task has terminated.
        /// </summary>
        public static event GlobalTaskTerminationHandler AnyTaskTerminated = delegate { };

        #endregion


        #region Static Methods

        /// <summary>
        /// Triggers the AnyTaskTerminated event.
        /// </summary>
        /// <param name="task">The task which has terminated.</param>
        /// <param name="reason">The reason for the tesk termination.</param>
        /// <param name="ex">The exception which triggered the task termination.</param>
        internal static void OnTaskTerminated(Task task, TaskTerminationReason reason, Exception ex)
        {
            GlobalTaskTerminationHandler evt = AnyTaskTerminated;

            if (evt != null)
                foreach (GlobalTaskTerminationHandler handler in evt.GetInvocationList())
                {
                    // call each subscribed handler, even if one throws an exception
                    try
                    {
                        handler(task, reason, ex);
                    }
                    // ReSharper disable EmptyGeneralCatchClause
                    catch
                    // ReSharper restore EmptyGeneralCatchClause
                    {
                    }
                }
        }

        #endregion


        #region Fields

        /// <summary>
        /// A lock used to synchronize this instance with calling threads
        /// </summary>
        internal readonly ManagedLock ExecutionLock;


        /// <summary>
        /// The phase in which this instance is
        /// </summary>
        private TaskPhase _currentPhase;


        /// <summary>
        ///The exception which is the cause of this instance's termination.
        /// </summary>
        private Exception _terminationException;


        /// <summary>
        /// The Thread associated to this instance
        /// </summary>
        internal readonly ThreadWrapper Thread;


        /// <summary>
        /// The state in which the current instance is waiting.
        /// </summary>
        private AcceptorWaitState _waitState;


        /// <summary>
        /// The next rendez-vous to be executed.
        /// </summary>
        /// <value>The next rendez vous.</value>
        private RendezVous _nextRendezVous;


        /// <summary>
        /// A flag indicating whether this instance is a Delay task.
        /// </summary>
        internal readonly bool IsDelayTask;

        #endregion


        #region Properties

        /// <summary>
        /// Sets a value indicating whether to auto activate this instance.
        /// This permits to create and activate a task in a single statement: <c>var t = new XxxTask { AutoActivate = true };</c>
        /// </summary>
        /// <value><c>true</c> if auto activate this instance; otherwise, <c>false</c>.</value>
        public bool AutoActivate
        {
            set
            {
                if (value && TaskStatus == TaskStatus.Inactive)
                    Activate();
            }
        }
        /// <summary>
        /// Gets or sets the desired <see cref="ThreadPriority"/> of this instance.
        /// </summary>
        /// <value>The <see cref="ThreadPriority"/> of this instance.</value>
        public override ThreadPriority Priority
        {
            get { return Thread.Priority; }
            set { Thread.Priority = value; }
        }


        /// <summary>
        /// Gets a value indicating the current <see cref="ThreadPriority"/> of the instance.
        /// This current <see cref="ThreadPriority"/> is normaly the value defined in the
        /// <see cref="ITask.Priority"/> but can differ due to Ada priority inheritance rules.
        /// </summary>
        /// <value>The current <see cref="ThreadPriority"/> of the instance</value>
        public ThreadPriority CurrentPriority
        {
            get { return Thread.CurrentPriority; }
        }


        /// <summary>
        /// Gets a value indicating whether the current instance is Callable.
        /// </summary>
        /// <value>
        ///     <c>true</c> if the current instance is Callable, <c>false</c> otherwise.
        /// </value>
        public bool Callable
        {
            get
            {
                switch (_currentPhase)
                {
                    case TaskPhase.Created:
                    case TaskPhase.Activating:
                    case TaskPhase.Executing:
                        return !Thread.IsAborting;
                    default:
                        return false;
                }
            }
        }


        /// <summary>
        /// Gets a value indicating whether the current instance is Terminated.
        /// </summary>
        /// <value>
        ///     <c>true</c> if the current instance is Terminated, <c>false</c> otherwise.
        /// </value>
        public bool Terminated
        {
            get { return _currentPhase == TaskPhase.Terminated; }
        }


        /// <summary>
        /// Gets a value indicating the current status of this instance.
        /// </summary>
        /// <value>The current status of this instance.</value>
        public TaskStatus TaskStatus
        {
            get
            {
                switch (_currentPhase)
                {
                    case TaskPhase.Created:
                        return TaskStatus.Inactive;
                    case TaskPhase.Activating:
                    case TaskPhase.Executing:
                    case TaskPhase.Completed:
                        if (Thread.IsAborting)
                            return TaskStatus.Abnormal;
                        return (Thread.ThreadState & System.Threading.ThreadState.WaitSleepJoin) != 0 ?
                            TaskStatus.Blocked :
                            TaskStatus.Ready;
                    case TaskPhase.Terminated:
                        return TerminationReason == TaskTerminationReason.Completion ?
                            TaskStatus.Terminated :
                            TaskStatus.Abnormal;
                    default:
                        return TaskStatus.Ready;
                }
            }
        }


        /// <summary>
        /// Gets or sets the name of this instance.
        /// </summary>
        /// <value>The name of this instance.</value>
        public override sealed string Name
        {
            get
            {
                return Thread == null ? base.Name : Thread.Name;
            }
            set
            {
                base.Name = value;
                if (Thread != null)
                    Thread.Name = value;
            }
        }


        /// <summary>
        /// Gets a value indicating whether this instance is suspended in a select
        /// statement containing an open terminate alternative.
        /// </summary>
        /// <value>
        ///     <c>true</c> if suspended with an openterminate alternative; otherwise, <c>false</c>.
        /// </value>
        internal bool SuspendedWithTerminate
        {
            get { return _waitState == AcceptorWaitState.WaitForCallOrTerminate; }
        }


        /// <summary>
        /// Gets or sets the reason why this instance has terminated.
        /// </summary>
        /// <value>The termination reason.</value>
        private TaskTerminationReason TerminationReason { get; set; }


        /// <summary>
        /// Gets or sets a value indicating whether this instance is suspended, waiting for an entry call.
        /// </summary>
        /// <value><c>true</c> if this instance is waiting for a call; otherwise, <c>false</c>.</value>
        private bool WaitingForCall
        {
            get
            {
                return _waitState != AcceptorWaitState.NoWait;
            }
        }

        #endregion


        #region Events

        /// <summary>
        /// A delegate used to handle the TaskTerminated event
        /// </summary>
        public delegate void TaskTerminationHandler(TaskTerminationReason reason, Exception exception);


        /// <summary>
        /// Occurs when this instance has terminated.
        /// </summary>
        public event TaskTerminationHandler TaskTerminated = delegate { };

        #endregion


        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Task"/> class.
        /// </summary>
        /// <param name="maxStackSize">Maximum size of the new instance's stack, or 0 to use the default stack size.</param>
        /// <exception cref="ArgumentOutOfRangeException">thrown when <paramref name="maxStackSize"/> is negative</exception>
        protected Task(int maxStackSize = 0)
            : this(false, maxStackSize)
        {
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="Task"/> class.
        /// </summary>
        /// <param name="isDelayTask">A flag indicating whether this instance is a Delay task.</param>
        /// <param name="maxStackSize">Maximum size of the new instance's stack, or 0 to use the default stack size.</param>
        /// <exception cref="ArgumentOutOfRangeException">thrown when <paramref name="maxStackSize"/> is negative</exception>
        internal Task(bool isDelayTask, int maxStackSize = 0)
            : base(true)
        {
            if (maxStackSize < 0)
                throw new ArgumentOutOfRangeException("maxStackSize");

            IsDelayTask = isDelayTask;

            // In the Ada norm, creating a task is potentially blocking because activation is automatic. In this implementation
            // this is not the case and the next test is commented
            //// Creation of a new task is a potentialy blocking operation therefore check that the call can block
            //if (!ThreadWrapper.CurrentThread.CanBlock)
            //    throw Errors.PotentiallyBlockingOperationNotAllowed("Task instanciation");

            // Create a new thread
            Thread thread = maxStackSize > 0 ? new Thread(ThreadStart) : new Thread(ThreadStart, maxStackSize);
            if (Name != null)
                thread.Name = Name;

            // Create the plumbing
            Thread = new ThreadWrapper(GetType().Name, thread);

            _currentPhase = TaskPhase.Created;

            ExecutionLock = new ManagedLock("MethodExecution", this);

            // Add this instance as a descendent of Master
            Master.AddDependent(this);
        }

        #endregion


        #region Methods

        /// <summary>
        /// Activates this instance.
        /// </summary>
        public void Activate()
        {
            Master.Activate(this);
        }


        /// <summary>
        /// Method which is executed when the thread associated to this instance is started.
        /// </summary>
        private void ThreadStart()
        {
            ThreadWrapper.CurrentThread = Thread;
            //CurrentRealTask = this;
            CurrentTask = this;

            _currentPhase = TaskPhase.Activating;

            try
            {
                new Block(ToString(),
                    declare:
                        RunDeclarativePart,
                    begin: () =>
                    {
                        Master.ActivationSucceded(this);
                        _currentPhase = TaskPhase.Executing;
                        Body();
                        _currentPhase = TaskPhase.Completed;
                        Thread.IgnoreFurtherAborts = true;
                        TerminationReason = TaskTerminationReason.Completion;
                    },
                    exception: OnException,
                    isTask: true);
            }
            finally
            {
                Thread.IgnoreFurtherAborts = true;
                ThreadFinish();
            }
        }


        /// <summary>
        /// Runs the declarative part of this instance.
        /// </summary>
        private void RunDeclarativePart()
        {
            try
            {
                // The declarative part of a task is run with the priority of the task creator
                Thread.StartPriorityInherit(Master.Priority);

                DeclarativePart();
            }
            catch (ThreadAbortException ex)
            {
                if (ThreadWrapper.IsIllegalAbort(ex))
                    Errors.FatalError("Using Thread.Abort is not allowed when using the nTasking library.");

                TerminationReason = TaskTerminationReason.Abortion;
                // LRM 9.2 (5): Any tasks that are aborted prior to completing their activation are ignored
                // when determining whether to raise Tasking_Error
                Master.ActivationSucceded(this);
                Thread.IgnoreFurtherAborts = true;
                throw;
            }
            catch (Exception ex)
            {
                TerminationReason = TaskTerminationReason.Exception;
                _terminationException = ex;
                Master.ActivationFailed(this);
                Thread.IgnoreFurtherAborts = true;
                throw;
            }
            finally
            {
                // Return the task priority to its defined value
                Thread.EndPriorityInherit();
            }
        }


        private bool OnException(Exception ex)
        {
            if (ex is ThreadAbortException)
            {
                ThreadAbortException ex2 = ex as ThreadAbortException;

                if (ThreadWrapper.IsIllegalAbort(ex2))
                    Errors.FatalError("Using Thread.Abort is not allowed when using the nTasking library.");

                if (ThreadWrapper.IsTaskTerminate(ex2))
                {
                    TerminationReason = TaskTerminationReason.Completion;
                    Tracer.Trace("Task terminated");
                }
                else
                {
                    TerminationReason = TaskTerminationReason.Abortion;
                    Tracer.Trace("Task aborted");
                }
                ThreadWrapper.ResetAbort();
                _currentPhase = TaskPhase.Completed;
            }
            else
            {
                Tracer.Trace("Task threw exception " + ex);

                TerminationReason = TaskTerminationReason.Exception;
                _terminationException = ex;

                Exception(ex);

                _currentPhase = TaskPhase.Completed;
            }

            return false;
        }


        /// <summary>
        /// Method which is executed when the thread associated to this instance terminates execution.
        /// </summary>
        private void ThreadFinish()
        {
            _currentPhase = TaskPhase.Completed;
            MethodCall[] pendingCalls;

            try
            {
                using (Thread.NoAbort())
                {
                    Tracer.Trace("Throwing TaskingError in all tasks which have entry calls queued on this task");

                    while (true)
                    {
                        using (ExecutionLock.Locked())
                        {
                            if (_nextRendezVous != null)
                                PendingCalls.Add(_nextRendezVous.Call);
                            pendingCalls = PendingCalls.ToArray();
                            PendingCalls.Clear();
                        }

                        if (pendingCalls.Length == 0)
                            break;

                        // Throw a TaskingError in all tasks which have still calls pending on this task
                        foreach (MethodCall pending in pendingCalls)
                        {
                            using (pending.CallLock.Locked())
                            {
                                if (pending.Finished)
                                    continue;

                                pending.CancelSelectiveCallAlternative();

                                pending.Context.Exception = Errors.UnacceptableCall(pending);
                                Finish(pending, true);
                            }
                        }
                    }

                    // Trigger the TaskTerminated event
                    OnTaskTerminated(TerminationReason, _terminationException);

                    Tracer.Trace("Signaling termination to Master");

                    // Signal to our master that this instance execution is finished
                    Master.RemoveDependent(this);

                    Master = null;
                }
            }
            finally
            {
                _currentPhase = TaskPhase.Terminated;
            }
        }


        /// <summary>
        /// Trigger the TaskTerminated event
        /// </summary>
        /// <param name="reason">The reason for the task termination.</param>
        /// <param name="ex">The exception which triggered the termination.</param>
        private void OnTaskTerminated(TaskTerminationReason reason, Exception ex)
        {
            Tracer.Trace("Triggering Global Termination event");

            // Trigger the static AnyTaskTerminated event
            OnTaskTerminated(this, reason, ex);

            Tracer.Trace("Triggering Local Termination event");

            TaskTerminationHandler evt = TaskTerminated;
            if (evt != null)
                // Call each of the event subscriber, even if one throws an exception
                foreach (TaskTerminationHandler handler in evt.GetInvocationList())
                    try
                    {
                        handler(reason, ex);
                    }
                    // ReSharper disable EmptyGeneralCatchClause
                    catch
                    // ReSharper restore EmptyGeneralCatchClause
                    {
                    }
        }


        /// <summary>
        /// Create a pseudo select/accept/end select to handle an accept outside a select statement
        /// </summary>
        /// <param name="statements">The pseudo select's statements.</param>
        private void Accept(Action<SelectiveAccept> statements)
        {
            SelectiveAccept s = OpenSelect(3);
            try
            {
                statements(s);
            }
            catch (Exception)
            {
                ExecutionLock.ReleaseLock();
                Thread.AbortResume();
                throw;
            }
            ExecuteSelect(s);
        }


        /// <summary>
        /// Creates an <see cref="MethodAccept"/> with a body for the specified entry.
        /// </summary>
        /// <typeparam name="CallDelegateT">A delegate type which defines the parameter profile of the entry.</typeparam>
        /// <param name="acceptCall">The entry to be accepted.</param>
        /// <param name="body">The body of the accept.</param>
        /// <param name="checkBody">if set to <c>true</c> check that parameter <paramref name="body"/> is not <c>null</c>.</param>
        /// <param name="exceptionHandler">The body's exception handler.</param>
        private void AcceptEntry<CallDelegateT>(CallDelegateT acceptCall, CallDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where CallDelegateT : class
        {
            Accept(s => SelectiveAcceptEx.AcceptEntry(s, acceptCall, body, checkBody, exceptionHandler));
        }


        /// <summary>
        /// Creates an <see cref="MethodAccept"/> with a body for the specified entry family member.
        /// </summary>
        /// <typeparam name="IndexT">The type of the index of the entry family.</typeparam>
        /// <typeparam name="CallDelegateT">A delegate type which defines the parameter profile of the entry.</typeparam>
        /// <typeparam name="AcceptDelegateT">The type of the ccept delegate T.</typeparam>
        /// <param name="acceptCall">The entry to be accepted.</param>
        /// <param name="body">The body of the accept.</param>
        /// <param name="checkBody">Whether the body of the accept must be checked for non-nullness</param>
        /// <param name="exceptionHandler">The body's exception handler.</param>
        private void AcceptMember<IndexT, CallDelegateT, AcceptDelegateT>(CallDelegateT acceptCall, AcceptDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where CallDelegateT : class
            where AcceptDelegateT : class
        {
            Accept(s => SelectiveAcceptEx.AcceptMember<IndexT, CallDelegateT, AcceptDelegateT>(s, acceptCall, body, checkBody, exceptionHandler));
        }


        /// <summary>
        /// Creates an <see cref="MethodAccept"/> with a body for the specified entry family member.
        /// </summary>
        /// <typeparam name="IndexT">The type of the index of the entry family.</typeparam>
        /// <typeparam name="AcceptDelegateT">The type of the ccept delegate T.</typeparam>
        /// <param name="family">The entry family to be accepted.</param>
        /// <param name="body">The body of the accept.</param>
        /// <param name="checkBody">if set to <c>true</c> check that parameter <paramref name="body"/> is not <c>null</c>.</param>
        /// <param name="exceptionHandler">The body's exception handler.</param>
        private void AcceptFamily<IndexT, AcceptDelegateT>(IExecutable family, AcceptDelegateT body, bool checkBody, Func<Exception, bool> exceptionHandler)
            where AcceptDelegateT : class
        {
            Accept(s => SelectiveAcceptEx.AcceptFamily<IndexT, AcceptDelegateT>(s, family, body, checkBody, exceptionHandler));
        }


        /// <summary>
        /// Starts a new select statement.
        /// </summary>
        /// <returns></returns>
        private SelectiveAccept OpenSelect(int skipFrames, bool isSelect = false)
        {
            // Check that the Select or Accept statement which triggered this method is correctly
            // placed in the Body of the task when using strict Ada compatibility mode
            if (Settings.AcceptSelectPlacement == AdaCompatibilityMode.StrictCompatibility)
            {
                StackTrace sTrace = new StackTrace(skipFrames);
                string methodName = sTrace.GetFrame(1).GetMethod().Name;

                if (methodName != "Body" && !methodName.StartsWith("<Body>"))
                {
                    Tracer.Trace("Invalid Select/Accept placement in " + methodName);
                    throw Errors.TaskCallNotInBody(this);
                }
            }
            else if (CurrentTask != this)
            {
                // The method containing the select or accept statement is accessed from a different task
                throw Errors.SelectAcceptCalledFromOutsideTask(this, CurrentTask);
            }
            else if (_currentPhase != TaskPhase.Executing)
            {
                // Make sure that accepts or select ca'nt be performed while not in the execution of the task body
                throw Errors.TaskCallNotFromBody(this);
            }

            Thread.AbortDefer();

            // NOTE: The lock taken here is released either in AcceptAny/Select upon exception
            // or in DoExecuteSelect (via calls to GetNextRendezVous or Suspend)
            ExecutionLock.GetLock();

            // Make sure that all entries are marked as non-acceptable
            foreach (SynchronizedMethod method in SynchronizedMethods)
                method.AcceptableCalls.Clear();

            return new SelectiveAccept(this);
        }


        /// <summary>
        /// Executes the select until one of the alternatives has been executed.
        /// </summary>
        /// <param name="s">The select.</param>
        internal void ExecuteSelect(SelectiveAccept s)
        {
            try
            {
                DoExecuteSelect(s);
            }
            catch (RendezVousError ex)
            {
                throw ex.InnerException;
            }
        }


        /// <summary>
        /// Wait until one of the branches of the specified select is chosen.
        /// </summary>
        /// <param name="s">The select.</param>
        /// <returns>A rendez-vous if one of the accept branch has been chose, or <c>null</c> otherwise</returns>
        private RendezVous SelectAlternative(SelectiveAccept s)
        {
            try
            {
                if (ReferenceEquals(s.Timeout, Timeout.Infinite))
                    s.Timeout = null;
                else if (s.Timeout != null)
                    s.Timeout.Freeze();

                RendezVous acceptedRendezVous = GetNextRendezVous();

                if (acceptedRendezVous != null || s.Else != null)
                {
                    // A rendez-vous is possible or an else alternative exist
                    Tracer.Trace(acceptedRendezVous != null
                                     ? "   Selecting an Accept alternative"
                                     : "   Selecting the Else part");
                }
                else if (s.Timeout != null)
                {
                    // Wait for the timeout or for a new acceptable call
                    Tracer.Trace("   Waiting for an entry call or Delay alternative expiration");
                    acceptedRendezVous = Suspend(s.Timeout.Value);
                }
                else if (s.HasTerminate)
                {
                    // Wait for a new acceptable call or task can terminate
                    Tracer.Trace("   Waiting for an entry call or Terminate alternative");
                    acceptedRendezVous = SuspendOrTerminate();
                }
                else if (s.OpenEntries == 0)
                {
                    // Check that at least one entry is opened
                    Tracer.Trace("   No open alternatives");
                    throw Errors.SelectHasNoOpenAlternative(this);
                }
                else
                {
                    // Wait indefinitely for an acceptable call
                    Tracer.Trace("   Waiting for an entry call");
                    acceptedRendezVous = Suspend(AcceptorWaitState.WaitForCall);
                }

                return acceptedRendezVous;
            }
            finally
            {
                ExecutionLock.ReleaseLock();
            }
        }


        /// <summary>
        /// Executes the select until one of the alternatives has been executed.
        /// </summary>
        /// <param name="s">The select.</param>
        internal void DoExecuteSelect(SelectiveAccept s)
        {
            RendezVous acceptedRendezVous = null;
            RendezVous lastNonRequeuedRendezVous = null;

            try
            {
                // Wait until one of the alternatives is selected
                acceptedRendezVous = SelectAlternative(s);

                // Process the acceptable call (if any) and any requeue
                if (acceptedRendezVous != null)
                {
                    // In case we are executing a select which simply wraps an Accept,
                    // clear the list of acceptable call so that a requeue on an other member
                    // of an entry family can't be accepted imediately
                    if (!s.IsSelect)
                        acceptedRendezVous.Call.Target.AcceptableCalls.Clear();

                    lastNonRequeuedRendezVous = acceptedRendezVous.Execute(Thread);
                }
                else
                    Thread.AbortResume();

            }
            finally
            {
                if (acceptedRendezVous != null)
                    acceptedRendezVous.Call.CallLock.ReleaseLock();
            }

            if (lastNonRequeuedRendezVous != null)
                // A call was accepted, process any statement following the accept body
                lastNonRequeuedRendezVous.MethodAccept.Continue();
            else if (s.Timeout != null)
                // A timeout occured, process any statement following the delay statement
                s.Timeout.Continue();
            else if (s.Else != null)
                // The else alternative was selected, processed the corresponding statements
                s.Else();
        }


        /// <summary>
        /// Suspends this instance until the specified timeout or a rendez-vous.
        /// </summary>
        /// <param name="timeout">The timeout.</param>
        /// <returns>The next rendez-vous to be accepted or<c>null</c> if the timeout occurred</returns>
        internal RendezVous Suspend(DateTime timeout)
        {
            return Suspend(timeout, AcceptorWaitState.WaitForCallOrTimeout);
        }


        /// <summary>
        /// Suspends this instance until a rendez-vous or terminate.
        /// </summary>
        /// <returns>The next rendez-vous to be accepted</returns>
        internal RendezVous SuspendOrTerminate()
        {
            return Master.SuspendOrTerminate(this, () => Suspend(AcceptorWaitState.WaitForCallOrTerminate));
        }


        /// <summary>
        /// Suspends this instance until the specified timeout or a rendez-vous.
        /// </summary>
        /// <param name="timeout">The timeout.</param>
        /// <param name="waitState">State of this intance during the wait.</param>
        /// <returns>The next rendez-vous to be accepted or<c>null</c> if the timeout occurred</returns>
        private RendezVous Suspend(DateTime timeout, AcceptorWaitState waitState)
        {
            try
            {
                _waitState = waitState;

                while (true)
                {
                    bool signalReceived;

                    using (Thread.WithAbort())
                    {
                        if (timeout == DateTime.MaxValue)
                        {
                            ExecutionLock.WaitLock();
                            signalReceived = true;
                        }
                        else
                            signalReceived = ExecutionLock.WaitLock(timeout);
                    }

                    if (!signalReceived)
                    {
                        // No rendez vous is ready, we dont need the lock anymore. In case a rendez vous is ready
                        // the lock is released in GetNextRendezVous
                        return null;
                    }

                    RendezVous rdv = GetNextRendezVous();

                    if (rdv != null || IsDelayTask)
                        return rdv;
                }
            }
            finally
            {
                _waitState = AcceptorWaitState.NoWait;
            }
        }


        /// <summary>
        /// Suspends this instance until the next rendez-vous.
        /// </summary>
        /// <param name="waitState">State of this intance during the wait.</param>
        /// <returns>The next rendez-vous to be accepted</returns>
        private RendezVous Suspend(AcceptorWaitState waitState)
        {
            return Suspend(DateTime.MaxValue, waitState);
        }


        /// <summary>
        /// Gets the next rendez-vous to be accepted or <c>null</c> if none is acceptable.
        /// </summary>
        /// <returns>The next rendez-vous</returns>
        internal RendezVous GetNextRendezVous()
        {
            // SYNCHRONIZATION:
            // ================
            // This method executed by the task's thread
            //
            // Upon entering this method, the caller holds the task's ExecutionLock
            //
            // If a rendez-vous is possible, the ExecutionLock is temporarily released
            // in order to acquire the CallLock of the rendez-vous. Then the ExecutionLock
            // is re-acquired in order to respect the order of locking (always acquire
            // CallLock first and then ExecutionLock).
            //
            // Upon exit, the caller will hold the ExecutionLock and (if a rendez-vous
            // is possible) the CallLock.

            // Method executed in the context of the Acceptor
            RendezVous nextRendezVous;

            while (true)
            {
                if (_nextRendezVous != null)
                {
                    // No need to abortDefer the call thread since this has been done in TryNextRendezVous
                    nextRendezVous = _nextRendezVous;
                    _nextRendezVous = null;
                }
                else
                {
                    nextRendezVous = FirstRendezVous();

                    if (nextRendezVous == null)
                        return null;

                    PendingCalls.Remove(nextRendezVous.Call);
                }

                using (ExecutionLock.Unlocked())
                {
                    nextRendezVous.Call.CallLock.GetLock();

                    // Cancel the alternative part of a selective call (i.e. abortable part or timeout)
                    if (!IsDelayTask)
                        nextRendezVous.Call.CancelSelectiveCallAlternative();

                    if (!nextRendezVous.Call.Finished)
                        return nextRendezVous;

                    nextRendezVous.Call.CallLock.ReleaseLock();
                }
            }
        }


        /// <summary>
        /// Enqueue the specified call and check if it can be accepted immediately
        /// </summary>
        /// <param name="call">The call.</param>
        /// <param name="tryOnly">if set to <c>true</c> the specified call is removed from the queue if it can't be accepted immediately.</param>
        /// <returns>Returns <c>true</c> if the call can be accepted immediately; <c>false</c> otherwise</returns>
        private void Enqueue(MethodCall call, bool tryOnly)
        {
            // Add the call to the list of pending calls
            PendingCalls.Add(call);

            try
            {
                if (!WaitingForCall)
                    // The task is not waiting for a call
                    return;

                if (_nextRendezVous != null)
                    // There is already an immediate rendez-vous waiting for acceptance
                    return;

                RendezVous rendezVous = FirstRendezVous();

                if (rendezVous == null)
                    // New call can't be accepted
                    return;

                if (rendezVous.Call != call)
                {
                    if (!tryOnly && WaitingForCall)
                        // There is a call with higher priority than the current call which can now be accepted
                        // because the addition of the call changed the barrier condition
                        ExecutionLock.Pulse();

                    return;
                }

                // At this point the new call can be accepted immediately

                // Remove the call from the queue
                PendingCalls.Remove(call);

                // Mark the rendez-vous as immediately acceptable
                rendezVous.Immediate = true;

                // Store the rendez-vous for immediate processing
                _nextRendezVous = rendezVous;

                // Wake-up the acceptor
                ExecutionLock.Pulse();

                using (ExecutionLock.Unlocked())
                {
                    // Wait for acceptance of the rendez-vous
                    rendezVous.Call.CallLock.WaitLock();
                }

                return;
            }
            finally
            {
                if (tryOnly)
                    // If the call can't be accepted immediately and it was
                    // for trial only, remove the call from the queue
                    PendingCalls.Remove(call);
            }
        }


        /// <summary>
        /// Perform a synchronous call of an entry or protected procedure or function.
        /// </summary>
        /// <typeparam name="RetT">The type of the call result.</typeparam>
        /// <typeparam name="ExecuteDelegateT">A delegate type representing the call's execution profile.</typeparam>
        /// <param name="call">The call.</param>
        /// <returns>The result of the synchronous call</returns>
        internal override RetT SynchronousCall<RetT, ExecuteDelegateT>(MethodCall call)
        {
            SynchronousEntryCall(call);
            return ((CallContext<RetT>)call.Context).Result;
        }


        /// <summary>
        /// Perform a call on an entry.
        /// </summary>
        /// <param name="call">The entry call to be performed</param>
        /// <param name="tryOnly">if set to <c>true</c> perform the call only if it can be accepted immediately.</param>
        internal override void EntryCall(MethodCall call, bool tryOnly)
        {
            if (call.Thread == Thread)
                throw Errors.SelfEntryCall(call.Target);

            using (ExecutionLock.Locked())
            {
                if (!Callable)
                    throw Errors.UnacceptableCall(call);

                Enqueue(call, tryOnly);
            }
        }


        /// <summary>
        /// Requeues an entry call to a new entry call.
        /// This method is executed within the context of the task/protected type declaring the entry
        /// onto which the call is requeued.
        /// </summary>
        /// <param name="call">The call to be requeued</param>
        /// <param name="requeued">The entry to which the call is requeued</param>
        /// <param name="internalRequeue">Indicate whether the requeue is an internal requeue or an external requeue</param>
        /// <returns></returns>
        internal override MethodAccept Requeue(MethodCall call, MethodCall requeued, bool internalRequeue)
        {
            using (ExecutionLock.Locked())
            {
                if (!Callable)
                    throw Errors.UnacceptableRequeue(call, this);

                call.Requeued = requeued;

                // Add the call to the list of pending calls
                PendingCalls.Add(call);

                if (!internalRequeue && !WaitingForCall)
                    return null;

                // There is already a rendez-vous which is planned to be accepted
                if (_nextRendezVous != null)
                    return null;

                RendezVous rendezVous = FirstRendezVous();

                if (rendezVous == null || rendezVous.Call != call)
                    return null;

                if (!internalRequeue)
                {
                    _nextRendezVous = rendezVous;
                    ExecutionLock.Pulse();
                }

                PendingCalls.Remove(call);

                return rendezVous.MethodAccept;
            }
        }


        /// <summary>
        /// Finishes a synchronized method call.
        /// </summary>
        /// <param name="call">The call to  finish.</param>
        /// <param name="signalCaller">When <c>true</c> wake up the caller thread</param>
        internal override void Finish(MethodCall call, bool signalCaller)
        {
            if (call.Finished)
                return;

            using (ThreadWrapper.CurrentThread.NoAbort())
            {
                using (ExecutionLock.Locked())
                {
                    DoFinish(call, signalCaller);
                }
            }
        }


        /// <summary>
        /// Cancel a pending call to a synchronized operation
        /// </summary>
        /// <param name="call">The call to cancel</param>
        /// <param name="signalCaller">When <c>true</c> wake up the caller thread</param>
        internal override void Cancel(MethodCall call, bool signalCaller)
        {
            Tracer.Trace("Cancelling entry call " + call);
            Finish(call, signalCaller);
        }


        /// <summary>
        /// Signal that a call has had it's caller task priority lowered
        /// </summary>
        /// <param name="call">The call for which the priority has been lowered</param>
        internal override void PriorityLowered(MethodCall call)
        {
            if (!ExecutionLock.TryGetLock()) return;

            PendingCalls.PriorityLowered(call);

            ExecutionLock.ReleaseLock();
        }


        /// <summary>
        /// Signal that a call has had it's caller task priority raised
        /// </summary>
        /// <param name="call">The call for which the priority has been raised</param>
        internal override void PriorityRaised(MethodCall call)
        {
            if (!ExecutionLock.TryGetLock()) return;

            PendingCalls.PriorityLowered(call);

            ExecutionLock.ReleaseLock();
        }


        /// <summary>
        /// Terminates this instance.
        /// </summary>
        internal void Terminate()
        {
            Thread.Terminate();
        }


        /// <summary>
        /// Starts the activation of this instance.
        /// </summary>
        internal void StartActivation()
        {
            Thread.Start();
        }


        /// <summary>
        /// Aborts the current instance.
        /// </summary>
        public void Abort()
        {
            if (_currentPhase == TaskPhase.Created || _currentPhase == TaskPhase.Completed)
                TerminationReason = TaskTerminationReason.Abortion;

            Thread.Abort(TaskStatus == TaskStatus.Inactive, ThreadFinish);
        }


        /// <summary>
        /// Wait for the current instance's thread to terminate.
        /// </summary>
        internal void Join()
        {
            Thread.Join();
        }


        /// <summary>
        /// Represents the declarative part of the task.
        /// </summary>
        protected virtual void DeclarativePart()
        {
        }


        /// <summary>
        /// Represent task's body.
        /// </summary>
        protected abstract void Body();


        /// <summary>
        /// Represents the task body's exception handler.
        /// </summary>
        /// <param name="ex">The exception which has been thrown by the task body's execution</param>
        protected virtual void Exception(Exception ex)
        {
        }


        // TODO : document exceptions which can be thrown
        /// <summary>
        /// Start a new select statement.
        /// </summary>
        protected void Select(Func<SelectiveAccept.AfterSelect, SelectiveAccept.Complete> statements)
        {
            if (statements == null)
                throw Errors.StatementsIsNull();

            // Check that the select/accept is not within the abortable part of a selective call
            if (Thread.InAbortablePart)
                throw Errors.SelectNotAllowedInAbortablePart(this);

            // Check that this instance can be blocked since select is an Ada
            // 'potentially blocking operation'
            if (!Thread.CanBlock)
                throw Errors.PotentiallyBlockingOperationNotAllowed("select");

            SelectiveAccept s = OpenSelect(1, true);
            try
            {
                statements(new SelectiveAccept.AfterSelect { Select = s });
            }
            catch (Exception)
            {
                ExecutionLock.ReleaseLock();
                Thread.AbortResume();
                throw;
            }
            ExecuteSelect(s.Select);
        }

        #endregion

    }

}
